package cn.lingyangwl.agile.common.security.model;

import cn.lingyangwl.agile.common.security.constants.DataRuleCodeEnum;
import cn.lingyangwl.agile.common.security.constants.DataRuleEnum;
import cn.lingyangwl.agile.common.security.utils.SecurityUtils;
import cn.lingyangwl.framework.core.utils.spring.SpringUtils;
import cn.lingyangwl.framework.tool.core.CollectionUtils;
import cn.lingyangwl.framework.tool.core.SqlUtils;
import cn.lingyangwl.framework.tool.core.StringUtils;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import cn.hutool.core.util.StrUtil;
import cn.lingyangwl.agile.model.constants.DataRuleCons;
import cn.lingyangwl.agile.model.module.perm.DataRuleContextHolder;
import cn.lingyangwl.agile.model.module.auth.LoginUser;
import cn.lingyangwl.agile.model.module.auth.UserDataRule;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author shenguangyang
 */
public class DataRuleGenerator {
    private static final Logger log = LoggerFactory.getLogger(DataRuleGenerator.class);

    public static void initSqlCondition(Class<?> entityClass, DataRuleCodeEnum dataRuleCodeEnum) {
        StopWatch watch = StopWatch.createStarted();
        try {
            DataRule dataRule = getRule(dataRuleCodeEnum);
            Map<String, UserDataRule> ruleMap = dataRule.getDataRuleMap();

            if (!ruleMap.isEmpty()) {
                PropertyDescriptor[] origDescriptors = PropertyUtils.getPropertyDescriptors(entityClass);
                StringBuilder dataRuleSql = new StringBuilder();
                String name, type, column;
                // 根据不同的数据规则拼接过滤条件
                for (PropertyDescriptor origDescriptor : origDescriptors) {
                    name = origDescriptor.getName();
                    type = origDescriptor.getPropertyType().toString();

                    // 判断ruleColumn是否等于字段名称
                    if (!ruleMap.containsKey(name)) {
                        continue;
                    }
                    String tableFieldName = DataRuleCons.UserTable.ALIAS + "." + getTableFieldName(entityClass, name);
                    UserDataRule rule = ruleMap.get(name);
                    String conditions = rule.getRuleConditions();
                    String ruleValue = rule.getRuleValue();

                    if (DataRuleEnum.GT.getCondition().equals(conditions)) {
                        Object data = parseByType(ruleValue, type, DataRuleEnum.GT);
                        if (data instanceof String) {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" > \"").append(data).append("\" ) ");
                        } else {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" > ").append(data).append(" ) ");
                        }

                    } else if (DataRuleEnum.GE.getCondition().equals(conditions)) {
                        Object data = parseByType(ruleValue, type, DataRuleEnum.GE);
                        if (data instanceof String) {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" >= \"").append(data).append("\" ) ");
                        } else {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" >= ").append(data).append(" ) ");
                        }

                    } else if (DataRuleEnum.LT.getCondition().equals(conditions)) {
                        Object data = parseByType(ruleValue, type, DataRuleEnum.LT);
                        if (data instanceof String) {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" < \"").append(data).append("\" ) ");
                        } else {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" < ").append(data).append(" ) ");
                        }
                    } else if (DataRuleEnum.LE.getCondition().equals(conditions)) {
                        Object data = parseByType(ruleValue, type, DataRuleEnum.LT);
                        if (data instanceof String) {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" <= \"").append(data).append("\" ) ");
                        } else {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" <= ").append(data).append(" ) ");
                        }
                    } else if (DataRuleEnum.EQ.getCondition().equals(conditions)) {
                        Object data = parseByType(ruleValue, type, DataRuleEnum.EQ);
                        if (data instanceof String) {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" = \"").append(data).append("\" ) ");
                        } else {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" = ").append(data).append(" ) ");
                        }

                    } else if (DataRuleEnum.NE.getCondition().equals(conditions)) {
                        Object data = parseByType(ruleValue, type, DataRuleEnum.NE);
                        if (data instanceof String) {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" != \"").append(data).append("\" ) ");
                        } else {
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" != ").append(data).append(" ) ");
                        }

                    } else if (DataRuleEnum.IN.getCondition().equals(conditions)) {
                        // 要求以逗号分割
                        List<String> inDataList = StringUtils.toList(ruleValue, ",", String.class);
                        if (CollectionUtils.isNotEmpty(inDataList)) {
                            // 取第一个数据, 判断类型
                            Object data = parseByType(inDataList.get(0), type, DataRuleEnum.IN);
                            StringBuilder inSql = new StringBuilder();
                            if (data instanceof String) {
                                for (String inData : inDataList) {
                                    inSql.append(" \"").append(inData).append("\", ");
                                }
                            } else {
                                for (String inData : inDataList) {
                                    inSql.append(inData).append(", ");
                                }
                            }
                            dataRuleSql.append(" AND ( ").append(tableFieldName).append(" in (  ").append(inSql.substring(0, inSql.lastIndexOf(","))).append(" ) ) ");
                        }

                    } else if (DataRuleEnum.LIKE.getCondition().equals(conditions)) {
                        Object data = parseByType(ruleValue, type, DataRuleEnum.LIKE);
                        dataRuleSql.append(" AND ( ").append(tableFieldName).append(" LIKE \"%").append(data).append("%\" ) ");
                    } else if (DataRuleEnum.LEFT_LIKE.getCondition().equals(conditions)) {
                        Object data = parseByType(ruleValue, type, DataRuleEnum.LEFT_LIKE);
                        dataRuleSql.append(" AND ( ").append(tableFieldName).append(" LIKE \"%").append(data).append("\" ) ");
                    } else if (DataRuleEnum.RIGHT_LIKE.getCondition().equals(conditions)) {
                        Object data = parseByType(ruleValue, type, DataRuleEnum.RIGHT_LIKE);
                        dataRuleSql.append(" AND ( ").append(tableFieldName).append(" LIKE \"").append(data).append("%\" ) ");
                    }
                }
                if (StringUtils.isNotEmpty(dataRuleSql.toString())) {
                    String newSql = dataRuleSql.substring(4);
                    DataRuleContextHolder.get().setDataRuleSql(newSql);
                }
            }

            // 处理数据范围规则 (eg: 只看自己 or 查看当前部门数据 or 查看所属部门的全部数据)
            List<UserDataRule> dataScopeList = dataRule.getUserDataRuleList();
            if (dataScopeList.isEmpty()) {
                return;
            }
            LoginUser loginUser = SecurityUtils.getLoginUser();
            StringBuilder dataScopeSql = new StringBuilder();
            // 生成数据范围sql
            dataScopeFilter(dataScopeSql, loginUser, dataScopeList);
            // 如果满足任意条件, 就覆盖@DataScope注解所生成的dataScopeSql, 也就是说, 以数据规则配置的可见范围为准, 而忽略角色配置的范围
            if (StringUtils.isNotBlank(dataScopeSql.toString())) {
                // 改sql语句是拼接在查询语句 where的后边且以AND进行拼接m ==> 去掉 ‘ or’
                String sql = "(" + dataScopeSql.substring(4) + ") ";
                DataRuleContextHolder.get().setDataScopeSql(" AND " + sql);
            }
        } finally {
            log.debug("initSqlCondition time: {} ms, dataRuleSql: {}, dataScopeSql: {}",
                    watch.getTime(TimeUnit.MILLISECONDS), DataRuleContextHolder.get().getDataRuleSql(),
                    DataRuleContextHolder.get().getDataScopeSql());
            watch.stop();
        }
    }

    @Getter
    @Setter
    private static class DataRule {
        /**
         * 数据规则map
         */
        Map<String, UserDataRule> dataRuleMap;
        /**
         * 数据范围list, 其实也是数据规则的一种, 这里分开用于实现根据部门过滤数据
         * (查看所属部门数据 or 查看当前部门数据 or 查看个人数据等等)
         */
        List<UserDataRule> userDataRuleList;
    }

    private static DataRule getRule(DataRuleCodeEnum dataRuleCodeEnum) {
        DataRule result = new DataRule();
        Map<String, UserDataRule> dataRuleMap = new HashMap<>();
        List<UserDataRule> dataScopeList = new ArrayList<>();

        result.setDataRuleMap(dataRuleMap);
        result.setUserDataRuleList(dataScopeList);

        LoginUser loginUser = SecurityUtils.getLoginUser();
//        List<UserDataRule> permissionDataRules = loginUser.getAuthority().getDataRules();
        List<UserDataRule> permissionDataRules = new ArrayList<>();
        if (CollectionUtils.isEmpty(permissionDataRules) || loginUser.getIsTenantOwner()) {
            return result;
        }

//        List<SysMenuDataRulePO> permissionDataRules = new ArrayList<>();
//        SysMenuDataRulePO data1 = new SysMenuDataRulePO();
//        data1.setRuleColumn("userName");
//        data1.setRuleConditions(QueryRuleEnum.NE.getCondition());
////        data1.setRuleValue("admin");
//        data1.setRuleValue("\"1\" ) OR ( 1 = 1");
//
//        SysMenuDataRulePO data2 = new SysMenuDataRulePO();
//        data2.setRuleColumn("userId");
//        data2.setRuleConditions(QueryRuleEnum.EQ.getCondition());
//        data2.setRuleValue("2");
//
//        SysMenuDataRulePO data3 = new SysMenuDataRulePO();
//        data3.setRuleColumn("nickName");
//        data3.setRuleConditions(QueryRuleEnum.RIGHT_LIKE.getCondition());
//        data3.setRuleValue("test");
//        permissionDataRules.add(data1);
//        permissionDataRules.add(data2);
//        permissionDataRules.add(data3);

        for (UserDataRule permissionDataRule : permissionDataRules) {
            String ruleColumn = permissionDataRule.getRuleColumn();
            String ruleConditions = permissionDataRule.getRuleConditions();
            String ruleCode = permissionDataRule.getRuleCode();
            if (!dataRuleCodeEnum.getCode().equals(ruleCode)) {
                continue;
            }
            // 如果是数据范围规则条件, 放到list中
            if (isDataScopeRule(ruleConditions)) {
                dataScopeList.add(permissionDataRule);
            } else if (StringUtils.isNotEmpty(ruleColumn)){
                dataRuleMap.put(ruleColumn, permissionDataRule);
            }

        }
        return result;
    }

    /**
     * 判断是否为数据范围条件
     * @param ruleConditions 规则条件
     */
    private static boolean isDataScopeRule(String ruleConditions) {
        return DataRuleEnum.DATA_MY.getCondition().equals(ruleConditions) ||
                DataRuleEnum.DATA_MY_ALL_DEPT.getCondition().equals(ruleConditions) ||
                DataRuleEnum.DATA_MY_CURRENT_DEPT.getCondition().equals(ruleConditions);
    }

    /**
     * 数据范围过滤
     * 所有表中的都需要存放一个用户id字段且名字必须为 user_id
     *
     * @param dataScopeSql 数据范围sql
     * @param dataScopeList 数据范围规则集合
     * @see DataRuleEnum#DATA_MY
     * @see DataRuleEnum#DATA_MY_ALL_DEPT
     * @see DataRuleEnum#DATA_MY_CURRENT_DEPT
     */
    private static void dataScopeFilter(StringBuilder dataScopeSql, LoginUser loginUser, List<UserDataRule> dataScopeList) {
        if (loginUser == null) {
            return;
        }
        Long currentDeptId = loginUser.getCurrentDeptId();
        currentDeptId = Objects.isNull(currentDeptId) ? -1 : currentDeptId;
        for (UserDataRule userDataRule : dataScopeList) {
            String ruleConditions = userDataRule.getRuleConditions();
            // 查询用户所属部门的数据
            if (DataRuleEnum.DATA_MY_ALL_DEPT.getCondition().equals(ruleConditions)) {
//                Set<Long> deptIds = loginUser.getAuthority().getDeptIds();
                Set<Long> deptIds = new HashSet<>();
                if (CollectionUtils.isEmpty(deptIds)) {
                    dataScopeSql.append(" OR 1 = 0 ");
                    return;
                }
                for (Long deptId : deptIds) {
                    dataScopeSql.append(StringUtils.format(" OR {}.{} = {} ",
                            DataRuleCons.UserDeptRelTable.ALIAS, DataRuleCons.UserDeptRelTable.COLUMN_DEPT_ID, deptId));
                }
            }  else if (DataRuleEnum.DATA_MY_CURRENT_DEPT.getCondition().equals(ruleConditions)) {
                // 查看当前部门数据
                dataScopeSql.append(" AND ").append(getSelectSpecifyDeptDataSql(currentDeptId));
            } else if (DataRuleEnum.DATA_MY.getCondition().equals(ruleConditions)){
                // 不查询任何数据
                dataScopeSql.append(StringUtils.format(" AND {}.user_id = {} ",
                        DataRuleCons.UserDeptRelTable.ALIAS, loginUser.getUserId()));
            }
        }
    }

    /**
     * 获取查询指定部门的数据 sql
     * @param specifyDeptId 指定的部门id
     */
    private static String getSelectSpecifyDeptDataSql(Long specifyDeptId) {
        // 查询指定部门数据
        String selectSpecifyDeptDataSql = "";
        if (ObjectUtils.isNotEmpty(specifyDeptId)) {
            // 改sql语句是拼接在查询语句 where的后边且以AND进行拼接
            // ( {}.dept_id = {} OR {}.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set({}, ancestors) ))
            selectSpecifyDeptDataSql = StringUtils.format(" ( {}.{} = {} OR {}.{} IN ( SELECT {}.{} FROM {} {} WHERE find_in_set({}, ancestors) ))",
                    DataRuleCons.UserDeptRelTable.ALIAS, DataRuleCons.UserDeptRelTable.COLUMN_DEPT_ID, specifyDeptId, /* {}.{} = {} */
                    DataRuleCons.UserDeptRelTable.ALIAS, DataRuleCons.UserDeptRelTable.COLUMN_DEPT_ID, /* OR {}.{} */
                    DataRuleCons.DeptTable.ALIAS, DataRuleCons.DeptTable.COLUMN_ID, /* SELECT {}.{} */
                    DataRuleCons.DeptTable.NAME, DataRuleCons.DeptTable.ALIAS, /* FROM {} {} */
                    specifyDeptId); /* WHERE find_in_set({}, ancestors) )) */
        }
        return selectSpecifyDeptDataSql;
    }


    /**
     * 获取表字段名
     *
     */
    private static String getTableFieldName(Class<?> clazz, String name) {
        try {
            //如果字段加注解了@TableField(exist = false),不走DB查询
            Field field = null;
            try {
                field = clazz.getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                //e.printStackTrace();
            }

            //如果为空，则去父类查找字段
            if (field == null) {
                List<Field> allFields = getClassFields(clazz);
                List<Field> searchFields = allFields.stream().filter(a -> a.getName().equals(name)).collect(Collectors.toList());
                if (searchFields.size() > 0) {
                    field = searchFields.get(0);
                }
            }

            if (field != null) {
                TableField tableField = field.getAnnotation(TableField.class);
                if (tableField != null) {
                    if (!tableField.exist()) {
                        //如果设置了TableField false 这个字段不需要处理
                        return null;
                    } else {
                        String column = tableField.value();

                        // 如果column中的值和name一样, 则默认进行驼峰转换
                        if (column.equals(name)) {
                            return StrUtil.toUnderlineCase(name);
                        }

                        //如果设置了TableField value 这个字段是实体字段
                        if (!"".equals(column)) {
                            return column;
                        }
                    }
                } else {
                    return StrUtil.toUnderlineCase(name);
                }
            }
        } catch (Exception e) {
            log.error("error: ", e);
            throw new BizException("规则查询失败");
        }
        return name;
    }

    /**
     * 获取class的 包括父类的
     *
     * @param clazz
     * @return
     */
    private static List<Field> getClassFields(Class<?> clazz) {
        List<Field> list = new ArrayList<>();
        Field[] fields;
        do {
            fields = clazz.getDeclaredFields();
            list.addAll(Arrays.asList(fields));
            clazz = clazz.getSuperclass();
        } while (clazz != Object.class && clazz != null);
        return list;
    }

    /**
     * 根据类型转换给定的值
     */
    private static Object parseByType(String value, String type, DataRuleEnum rule) {
        Object temp;
        if (SqlUtils.containsSqlInjection(value)) {
            throw new BizException("illegal rule value [{}]", value);
        }
        switch (type) {
            case "class java.lang.Integer":
                temp = Integer.parseInt(value);
                break;
            case "class java.math.BigDecimal":
                temp = new BigDecimal(value);
                break;
            case "class java.lang.Short":
                temp = Short.parseShort(value);
                break;
            case "class java.lang.Long":
                temp = Long.parseLong(value);
                break;
            case "class java.lang.Float":
                temp = Float.parseFloat(value);
                break;
            case "class java.lang.Double":
                temp = Double.parseDouble(value);
                break;
            case "class java.util.Date":
                // 暂时不支持根据时间数据规则查询
                temp = getDateQueryByRule(value, rule);
                break;
            default:
                temp = value;
                break;
        }
        return temp;
    }

    /**
     * 获取日期类型的值
     */
    private static Date getDateQueryByRule(String value, DataRuleEnum rule) {
        try {
            SimpleDateFormat time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            Date date = null;
            if (value.length() == 10) {
                if (rule == DataRuleEnum.GE) {
                    //比较大于
                    date = time.parse(value + " 00:00:00");
                } else if (rule == DataRuleEnum.LE) {
                    //比较小于
                    date = time.parse(value + " 23:59:59");
                }
                //TODO 日期类型比较特殊 可能oracle下不一定好使
            }
            if (date == null) {
                date = time.parse(value);
            }
            return date;
        } catch (ParseException e) {
            log.error("error: ", e);
            throw new BizException("规则查询失败");
        }
    }
}
